//索引模块

#pragma once

#include <iostream>
#include <vector>
#include <string>
#include<unordered_map>
#include<fstream>
#include"util.hpp"

#include<mutex>

namespace ns_index
{
    struct DocInfo
    {
        std::string title;
        std::string content;
        std::string url;
        uint64_t id;//文档id

        bool is_ad=false;//判断是否是广告
        int bid=0;//出价(普通文档为0)
        std::string keyword;//广告关键字
    };
    struct InvertedElem{
        uint64_t id;
        std::string word;//关键字
        int weight;//权重
        InvertedElem():weight(0){}//构造
    };
    typedef std::vector<InvertedElem> InvertedList;
    class Index
    {
    private:
    //构建单例模式，=禁止外部构造创建实例拷贝构造
        Index(){}
        Index(const Index&)=delete;
        Index& operator=(const Index&)=delete;
        static Index* instance;
        static std::mutex mtx;
    public:   
        ~Index(){}
        static Index* Getinstance()
        {
            if(instance==nullptr)//双重判断，如果存在直接返回实例，单层判断加锁解锁消耗资源
            {
            mtx.lock();
            if(instance==nullptr)
            {
                instance=new Index();
            }
            mtx.unlock();
            }
            return instance;
        }
    public:
        DocInfo* GetForwardIndex(uint64_t id)//根据id找文档内容
        {
            if(id >= forward_index.size())
            {
                std::cerr << "doc_id out range, error!" << std::endl;
                return nullptr;
            }
           return &forward_index[id];
        }
        InvertedList *GetInvertedList(const std::string &word)//根据关键字
            {
                auto iter = inverted_index.find(word);
                if(iter == inverted_index.end())
                {
                    std::cerr << word << " have no InvertedList" << std::endl;
                    return nullptr;
                }
                return &(iter->second);
            }
        //data/raw.html/raw.txt,处理完的在当前路径下
        int count=0;
        bool BuildIndex(const std::string &input)//建立索引
        {
            std::ifstream in(input,std::ios::in|std::ios::binary);
            if(!in.is_open())
            {
                std::cerr<<"open file "<<input<<"failed "<<std::endl;
                return false;
            }
            std::string line;
            while(std::getline(in,line))
            {
                DocInfo* doc=BuildForwardIndex(line);
                if(doc==nullptr)
                {
                    std::cerr<<"build ForwaraIndex failed"<<std::endl;
                    return false;
                }
                BuildInvertedIndex(*doc);
                 count++;
                    if(count % 50 == 0){
                        std::cout <<"当前已经建立的索引文档: " << count <<std::endl;
                        // LOG(NORMAL, "当前的已经建立的索引文档: " + std::to_string(count));
                    }
            }
            return true;
        }
        // 加载广告文档并构建索引（与普通文档共用正排和倒排索引）
        // 处理广告文档里广告
        int cmz=0;
        bool BuildAdvertIndex(const std::string& ad_path)
        {
            std::fstream in(ad_path,std::ios::in|std::ios::binary);
            if(!in.is_open())
            {
                std::cerr<<"open ad_path "<<ad_path<<std::endl;
                return false;
            }
            std::string line;
            while(std::getline(in,line))//读取广告
            {
                DocInfo* ad_doc=BuildAdForwardIndex(line);
                if (ad_doc == nullptr) 
                {
                std::cerr << "build ad forward index failed" << std::endl;
                continue;
                }
                BuildInvertedIndex(*ad_doc);
                cmz++;
                if(cmz%50==0)
                {
                    std::cout <<"当前已经建立的广告索引文档: " << count <<std::endl;
                }
            }
            return true;
        }

    private:
        DocInfo*BuildForwardIndex(const std::string&line)//正派索引
        {
            std::vector<std::string> results;
            std::string sep="\3";//行内分隔符
            ns_util::StringUtil::Split(line,&results,sep);
            if(results.size()!=3)
            {
                std::cerr<<"Spilt failed"<<std::endl;
                return nullptr;
            }
            DocInfo doc;
            doc.title=results[0];
            doc.content=results[1];
            doc.url=results[2];
            doc.id=forward_index.size();
            forward_index.push_back(std::move(doc));//防止拷贝消耗过大
            // return &doc;错误doc局部变量函数结束销毁1野指针
            return &forward_index.back();
        }
        bool BuildInvertedIndex(DocInfo &doc)//倒排
        {
            struct word_cnt//标题和内容词频
            {
                int title_cnt;
                int content_cnt;
                word_cnt():title_cnt(0), content_cnt(0){}
            };
            std::unordered_map<std::string,word_cnt> word_map;//暂时统计词频

            //标题分词
            std::vector<std::string> title_words;
            ns_util::JiebaUtil::CutString(doc.title, &title_words);
            //统计标题词频
            for(std::string word:title_words)
            {
                //这样大小写都是一样的词频Nn没区别
                boost::to_lower(word);
                word_map[word].title_cnt++;
            }
            //内容分词
            std::vector<std::string> content_words;
            ns_util::JiebaUtil::CutString(doc.content, &content_words);
            //统计内容词频
            for(std::string word:content_words)
            {
                boost::to_lower(word);
                word_map[word].content_cnt++;
            }
#define X 10
#define Y 1
            if(doc.is_ad)
            {
                std::vector<std::string> key_words;
                ns_util::JiebaUtil::CutString(doc.keyword, &key_words);
                for(std::string word:key_words)
                {
                    boost::to_lower(word);
                    word_map[word].title_cnt++;
                }
            }
            for(auto &word_pair:word_map)
            {
            InvertedElem a;
            a.id=doc.id;
            a.word=word_pair.first;
            if(doc.is_ad)
            {
            // 广告权重 = 价格*10（可调整） + 基础词频权重
            a.weight = doc.bid * 10 + (word_pair.second.title_cnt*X + word_pair.second.content_cnt*Y);
            }
            else
            {
            a.weight=word_pair.second.title_cnt*X+word_pair.second.content_cnt*Y;
            }
            InvertedList& invertedlist=inverted_index[word_pair.first];//如果关键字没有,就创建一个不用担心为空
            invertedlist.push_back(std::move(a));
            }
        return true;
        }
        //根据关键字找到对应的一组文档id
        // typedef std::vector<InvertedElem> InvertedList;为了看清变量用的
        // std::unordered_map<std::string, InvertedList> inverted_index;
        DocInfo*BuildAdForwardIndex(const std::string&line)
        {
            std::vector<std::string> parts;
            std::string sep="\3";//行内分隔符
            ns_util::StringUtil::Split(line,&parts,sep);
            // //测试广告分割是否成功
            std::cout << "分割后字段数: " << parts.size() << std::endl;
            for (size_t i = 0; i < parts.size(); ++i) 
            {
            std::cout << "字段[" << i << "]: " << parts[i] << std::endl;
            }
            if(parts.size()!=5)
            {
                std::cerr<<"ad format error: " << line << std::endl;
                return nullptr;
            }
            DocInfo doc;
            doc.keyword=parts[0];
            doc.title=parts[1];//parts[0]存关键词
            doc.content=parts[2];
            doc.url=parts[3];
            doc.bid=std::stoi(parts[4]);
            doc.is_ad=true;//是广告
            doc.id=forward_index.size();
            forward_index.push_back(doc);
            return &forward_index.back();
        }
    private:
        //正排索引的数据结构用数组，数组的下标天然是文档的ID
        std::vector<DocInfo> forward_index; //正排索引
        //倒排索引一定是一个关键字和一组(个)InvertedElem对应[关键字和倒排拉链的映射关系]
        std::unordered_map<std::string, InvertedList> inverted_index;
    };
    Index*Index:: instance=nullptr;//类外初始化
    std::mutex Index::mtx;
}
